#include <cassert>
#include <memory>
#include <sys/types.h>
#include "openvip/axi4_async.h"

namespace ovip {

void AXI4TaskRead::apply(AXI4MasterPortAS *master) {
    (*master->ar_port)["valid"]      = 1;
    (*master->ar_port)["bits_addr"]  = addr;
    (*master->ar_port)["bits_len"]   = len - 1;
    (*master->ar_port)["bits_size"]  = AXI4MasterPortAS::AxSIZE;
    (*master->ar_port)["bits_burst"] = INCR;
    master->data_read.clear();
}

void AXI4TaskReadBurst::apply(AXI4MasterPortAS *master) {
    (*master->ar_port)["valid"]      = 1;
    (*master->ar_port)["bits_addr"]  = addr;
    (*master->ar_port)["bits_len"]   = len - 1;
    (*master->ar_port)["bits_size"]  = AXI4MasterPortAS::AxSIZE;
    (*master->ar_port)["bits_burst"] = burst_type;
    master->data_read.clear();
}

void AXI4TaskWrite::apply(AXI4MasterPortAS *master) {
    (*master->aw_port)["valid"]      = 1;
    (*master->aw_port)["bits_addr"]  = addr;
    (*master->aw_port)["bits_len"]   = len - 1;
    (*master->aw_port)["bits_burst"] = INCR;
    (*master->aw_port)["bits_size"]  = AXI4MasterPortAS::AxSIZE;
    master->aw_busy                  = true;

    (*master->w_port)["valid"]       = 1;
    (*master->w_port)["bits_last"]   = 1;
    (*master->w_port)["bits_strb"]   = 0xff;
    (*master->w_port)["bits_data"].SetVU8(data);
    master->w_busy                   = true;
}

void AXI4TaskWriteBurst::apply(AXI4MasterPortAS *master) {
    (*master->aw_port)["valid"]      = 1;
    (*master->aw_port)["bits_addr"]  = addr;
    (*master->aw_port)["bits_len"]   = len - 1;
    (*master->aw_port)["bits_burst"] = burst_type;
    (*master->aw_port)["bits_size"]  = AXI4MasterPortAS::AxSIZE;
    master->aw_busy                  = true;

    for (const auto& item : data) {
        master->data_to_write.push(item);
    }
    (*master->w_port)["valid"]       = 1;
    (*master->w_port)["bits_data"].SetVU8(master->data_to_write.front());
                                          master->data_to_write.pop();
    (*master->w_port)["bits_last"]   = master->data_to_write.empty();
    (*master->w_port)["bits_strb"]   = 0xff;
    master->w_busy                   = true;
}

AXI4MasterPortAS::AXI4MasterPortAS(std::string name, xspcomm::XPort &dut_port, xspcomm::XClock &clk,
                               std::string prefix) {
    this->mName     = name;
    this->mPrefix   = prefix;
    this->clk       = &clk;
    this->all_ports = &dut_port.NewSubPort(prefix);
    this->ar_port   = &all_ports->NewSubPort("ar_");
    this->r_port    = &all_ports->NewSubPort("r_");
    this->aw_port   = &all_ports->NewSubPort("aw_");
    this->w_port    = &all_ports->NewSubPort("w_");
    this->b_port    = &all_ports->NewSubPort("b_");

    // Channel R
    clk.StepRis([this](u_int64_t c, void *args) {
        if ((*r_port)["valid"] && (*r_port)["ready"]) {
            data_read.push_back((*r_port)["bits_data"].GetVU8());
            if ((*r_port)["bits_last"]) {
                if (read_task_in_progress->type == READ)
                    ((AXI4TaskRead *)(read_task_in_progress.get()))->cb(this, data_read[0], args);
                else if (read_task_in_progress->type == READ_BURST)
                    ((AXI4TaskReadBurst *)(read_task_in_progress.get()))->cb(this, data_read, args);

                read_task_in_progress = nullptr;
                (*r_port)["ready"] = 0;
            }
        }
    }
    , nullptr, "AXI4MasterPortAS(" + this->mName + ")_R_Ris");

    // Channel AR
    clk.StepRis([this](u_int64_t c, void *args) {
        if ((*ar_port)["valid"] && (*ar_port)["ready"]) {
            (*ar_port)["valid"] = 0;
            (*r_port)["ready"]  = 1;
        }
    }
    , nullptr, "AXI4MasterPortAS(" + this->mName + ")_AR_Ris");

    // Channel B
    clk.StepRis([this](u_int64_t c, void *args) {
        if ((*b_port)["valid"] && (*b_port)["ready"]) {
            if (write_task_in_progress->type == WRITE)
                ((AXI4TaskWrite *)write_task_in_progress.get())->cb(this, (*b_port)["bits_resp"], args);
            else if (write_task_in_progress->type == WRITE_BURST)
                ((AXI4TaskWriteBurst *)write_task_in_progress.get())->cb(this, (*b_port)["bits_resp"], args);
            write_task_in_progress = nullptr;

            (*b_port)["ready"] = 0;
        }
    }
    , nullptr, "AXI4MasterPortAS(" + this->mName + ")_B_Ris");

    // Channel AW
    clk.StepRis([this](u_int64_t c, void *args) {
        if ((*aw_port)["valid"] && (*aw_port)["ready"]) {
            (*aw_port)["valid"] = 0;
            if (w_busy && !(*w_port)["valid"])
                (*b_port)["ready"] = 1;
        }
    }
    , nullptr, "AXI4MasterPortAS(" + this->mName + ")_AW_Ris");

    // Channel W
    clk.StepRis([this](u_int64_t c, void *args) {
        if ((*w_port)["valid"] && (*w_port)["ready"]) {
            if (data_to_write.empty()) {
                (*w_port)["valid"] = 0;
                if (aw_busy && !(*aw_port)["valid"])
                    (*b_port)["ready"] = 1;
            } else {
                (*w_port)["bits_data"].SetVU8(data_to_write.front()); data_to_write.pop();
                (*w_port)["bits_last"] = data_to_write.empty();
            }
        }
    }
    , nullptr, "AXI4MasterPortAS(" + this->mName + ")_W_Ris");

    // Load new task
    clk.StepRis([this](uint64_t c, void *args) {
        while (!task_queue.empty()) {
            auto &task = this->task_queue.front();
            if ((task->type == READ || task->type == READ_BURST)
                                            && read_task_in_progress == nullptr) {
                if (write_task_in_progress != nullptr) {
                    AXIAddr la = write_task_in_progress->addr, lb = task->addr;
                    AXIAddr ra = la + write_task_in_progress->len - 1, rb = lb + task->len - 1;
                    if (ra >= lb && rb >= la) break;
                }

                read_task_in_progress = std::move(task);
                this->task_queue.pop();
                read_task_in_progress->apply(this);
            } else if ((task->type == WRITE || task->type == WRITE_BURST)
                                           && write_task_in_progress == nullptr) {
                if (read_task_in_progress != nullptr) {
                    AXIAddr la = read_task_in_progress->addr, lb = task->addr;
                    AXIAddr ra = la + read_task_in_progress->len - 1, rb = lb + task->len - 1;
                    if (ra >= lb && rb >= la) break;
                }

                write_task_in_progress = std::move(task);
                this->task_queue.pop();
                write_task_in_progress->apply(this);
            } else { break; }
        }
    }, nullptr, "AXI4MasterPortAS(" + this->mName + ")_Task_Ris");


    reset_status();
}

AXI4MasterPortAS::~AXI4MasterPortAS() {
    delete all_ports;
    delete ar_port;
    delete r_port;
    delete aw_port;
    delete w_port;
    delete b_port;
}

bool AXI4MasterPortAS::is_busy() {
    return !task_queue.empty() || read_task_in_progress != nullptr
                               || write_task_in_progress != nullptr;
}

void AXI4MasterPortAS::reset_status() {
    all_ports->SetZero();
    while (!task_queue.empty())
        task_queue.pop();
    read_task_in_progress  = nullptr;
    write_task_in_progress = nullptr;
    data_read.clear();
    while (!data_to_write.empty())
        data_to_write.pop();
    aw_busy = false;
    w_busy  = false;
}

void AXI4MasterPortAS::read(AXIAddr addr, ReadCB read_cb) {
    task_queue.emplace(new AXI4TaskRead(addr, read_cb));
}

void AXI4MasterPortAS::read_burst(AXIAddr addr, int len, ReadBurstCB readburst_cb,
                                       BurstType burst_type) {
    task_queue.emplace(new AXI4TaskReadBurst(addr, len, readburst_cb, burst_type));
}

void AXI4MasterPortAS::write(AXIAddr addr, AXIData data, WriteCB write_cb) {
    task_queue.emplace(new AXI4TaskWrite(addr, data, write_cb));
}

void AXI4MasterPortAS::write_burst(AXIAddr addr, const AXIDataVec& data, WriteCB write_cb,
                                    BurstType burst_type) {
    task_queue.emplace(new AXI4TaskWriteBurst(addr, data, write_cb, burst_type));
}

}
